﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.ClientDW.Synchronization.RiskRatingsBenchmarks
{
    public class MarketCapBenchmark<T, TK, TV>
    {
        private const double DefaultMarketplaceCapitalization = 0;
        private readonly ComparativeGroup<T, TK, TV> _comparativeGroup;
        private Dictionary<int, T> _financialDataOrderedByMarket;
        private Dictionary<TK, int> _indexCompanyToMarketPosion;
        private Func<T, double?> _marketCapitalizationSelector;

        public MarketCapBenchmark(Func<T, double?> marketCapSelector, ComparativeGroup<T, TK, TV> comparativeGroup, IComparer<KeyValuePair<int, double>> comparer)
        {
            _comparativeGroup = comparativeGroup;
            var keySelector = comparativeGroup.KeySelector;
            _marketCapitalizationSelector = marketCapSelector;

            var sorted = comparativeGroup.Companies
               .Where(kvp => comparativeGroup.GetScore(keySelector(kvp)) > 0)
               .OrderBy(
                   kvp => new KeyValuePair<int, double>(comparativeGroup.GetGroupIndex(keySelector(kvp)), marketCapSelector(kvp) ?? -1),
                   comparer);

            int index = 0;
            var financialBuilder = new Dictionary<int, T>();
            var positionIndexBuilder = new Dictionary<TK, int>();
            foreach (var item in sorted)
            {
                positionIndexBuilder.Add(keySelector(item), index);
                financialBuilder.Add(index, item);
                index++;
            }

            _financialDataOrderedByMarket = financialBuilder;
            _indexCompanyToMarketPosion = positionIndexBuilder;
        }

        public Performance<T> GetIndustryPeers(TK companyId)
        {
            int maxElements = 5;
            int marketIndex;

            if (_indexCompanyToMarketPosion == null)
                return Performance<T>.Default;

            if (!_indexCompanyToMarketPosion.TryGetValue(companyId, out marketIndex))
                return Performance<T>.Default;

            var list = new List<CompanyScore<TK>>(maxElements);

            // int startIndex = -1;
            int groupId = _comparativeGroup.GetGroupIndex(companyId);

            var currentCompanyScore = GetCompanyScoreByMarket(marketIndex, groupId, 0);
            if (currentCompanyScore != null)
            {
                list.Add(currentCompanyScore);
            }

            int[] directions = { 1, -1 };
            byte found = 1;

            var groupCount = _comparativeGroup.GetComparativeGroupCount(groupId);
            if (groupCount < maxElements)
                maxElements = groupCount;

            int offset = 1;
            while (found < maxElements)
            {
                foreach (var direction in directions)
                {
                    var marketData = this.GetCompanyScoreByMarket(marketIndex, groupId, offset * direction);
                    if (marketData != null)
                    {
                        list.Add(marketData);
                        ++found;
                    }

                    if (found == maxElements)
                        break;
                }
                ++offset;
            }

            var range = ComputeMarketCapitalization(list);

            return new Performance<T> { MarketCapFrom = range.Item1, MarketCapTo = range.Item2, PeerList = list.OrderBy(c => c.Score).Select(x => _comparativeGroup.Company(x.Id)).ToList() };
        }

        private Tuple<double, double> ComputeMarketCapitalization(ICollection<CompanyScore<TK>> industryPeers)
        {
            double min = 0;
            double max = 0;

            if (industryPeers != null && industryPeers.Any())
            {
                var minMarkerCap = industryPeers.Min(p => p.MarketCapitalization);
                var maxMarkerCap = industryPeers.Max(p => p.MarketCapitalization);

                if (minMarkerCap > 0)
                {
                    min = minMarkerCap / 1000;
                }

                if (maxMarkerCap > 0)
                {
                    max = maxMarkerCap / 1000;
                }
            }

            return new Tuple<double, double>(min, max);
        }

        private CompanyScore<TK> GetCompanyScoreByMarket(int marketIndex, int group, int offset)
        {
            try
            {
                var newIndex = marketIndex + offset;
                if ((newIndex < 0) || (newIndex >= _financialDataOrderedByMarket.Count))
                    return null;

                var financialData = _financialDataOrderedByMarket[newIndex];
                var companyKey = _comparativeGroup.KeySelector(financialData);

                var score = _comparativeGroup.GetScore(companyKey);
                var rank = _comparativeGroup.GetBenchmark(companyKey);
                var newGroup = _comparativeGroup.GetGroupIndex(companyKey);

                if (newGroup != group || score.Equals(default(double?)) || rank.Equals(default(Benchmark<T>)))
                {
                    return null;
                }
                var companyScore = new CompanyScore<TK>
                {
                    Id = companyKey,
                    MarketCapitalization = _marketCapitalizationSelector(financialData) ?? DefaultMarketplaceCapitalization,
                    Score = score,
                };

                return companyScore;
            }
            catch (ArgumentOutOfRangeException)
            {
                return null;
            }
            catch (IndexOutOfRangeException)
            {
                return null;
            }
        }
    }
}